using System;

namespace PWEmu.ZLib
{
    /// <summary>
    /// 
    /// </summary>
	sealed class Inflate
	{
		private const int _MAX_WBITS = 15; // 32K LZ77 window
		
		// preset dictionary flag in zlib header
		private const int _PRESET_DICT = 0x20;
		
		private const int _Z_DEFLATED = 8;
		
		private const int _METHOD = 0; // waiting for method byte
		private const int _FLAG = 1; // waiting for flag byte
		private const int _DICT4 = 2; // four dictionary check bytes to go
		private const int _DICT3 = 3; // three dictionary check bytes to go
		private const int _DICT2 = 4; // two dictionary check bytes to go
		private const int _DICT1 = 5; // one dictionary check byte to go
		private const int _DICT0 = 6; // waiting for inflateSetDictionary
		private const int _BLOCKS = 7; // decompressing blocks
		private const int _CHECK4 = 8; // four check bytes to go
		private const int _CHECK3 = 9; // three check bytes to go
		private const int _CHECK2 = 10; // two check bytes to go
		private const int _CHECK1 = 11; // one check byte to go
		private const int _DONE = 12; // finished check, done
		private const int _BAD = 13; // got an error--stay here
		
		internal int _mode; // current inflate mode
		
		// mode dependent information
		internal int _method; // if FLAGS, method byte
		
		// if CHECK, check values to compare
		internal long[] _was = new long[1]; // computed check value
		internal long _need; // stream check value
		
		// if BAD, inflateSync's marker bytes count
		internal int _marker;
		
		// mode independent information
		internal int _nowrap; // flag for no wrapper
		internal int _wbits; // log2(window size)  (8..15, defaults to 15)
		
		internal InfBlocks _blocks; // current inflate_blocks state

        private static byte[] _mark = new byte[] { (byte)0, (byte)0, (byte)SupportClass.Identity(0xff), (byte)SupportClass.Identity(0xff) };
		
		internal int InflateReset(ZStream z)
		{
			if (z == null || z._istate == null)
                return ZLibConst.Status.Z_STREAM_ERROR;
			
			z.totalIn = z.totalOut = 0;
			z.message = null;
			z._istate._mode = z._istate._nowrap != 0?_BLOCKS:_METHOD;
			z._istate._blocks.reset(z, null);
            return ZLibConst.Status.Z_OK;
		}
		
		internal int InflateEnd(ZStream z)
		{
			if (_blocks != null)
				_blocks.free(z);
			_blocks = null;
			//    ZFREE(z, z->state);
            return ZLibConst.Status.Z_OK;
		}
		
		internal int InflateInit(ZStream z, int w)
		{
			z.message = null;
			_blocks = null;
			
			// handle undocumented nowrap option (no zlib header or check)
			_nowrap = 0;
			if (w < 0)
			{
				w = - w;
				_nowrap = 1;
			}
			
			// set window size
			if (w < 8 || w > 15)
			{
				InflateEnd(z);
                return ZLibConst.Status.Z_STREAM_ERROR;
			}
			_wbits = w;
			
			z._istate._blocks = new InfBlocks(z, z._istate._nowrap != 0?null:this, 1 << w);
			
			// reset state
			InflateReset(z);
            return ZLibConst.Status.Z_OK;
		}
		
		internal int Inflater(ZStream z, int f)
		{
			int r;
			int b;
			
			if (z == null || z._istate == null || z.nextIn == null)
                return ZLibConst.Status.Z_STREAM_ERROR;
            f = f == ZLibConst.StreamStatus.Z_FINISH ? ZLibConst.Status.Z_BUF_ERROR : ZLibConst.Status.Z_OK;
            r = ZLibConst.Status.Z_BUF_ERROR;
			while (true)
			{
				//System.out.println("mode: "+z.istate.mode);
				switch (z._istate._mode)
				{
					
					case _METHOD: 
						
						if (z.availableIn == 0)
							return r;

                        r = f;
						
						z.availableIn--; 
                        z.totalIn++;
						if (((z._istate._method = z.nextIn[z.nextInIndex++]) & 0xf) != _Z_DEFLATED)
						{
							z._istate._mode = _BAD;
							z.message = "Unknown compression method";
							z._istate._marker = 5; // can't try inflateSync
							break;
						}
						if ((z._istate._method >> 4) + 8 > z._istate._wbits)
						{
							z._istate._mode = _BAD;
							z.message = "Invalid window size";
							z._istate._marker = 5; // can't try inflateSync
							break;
						}
						z._istate._mode = _FLAG;
						goto case _FLAG;
					
					case _FLAG: 
						
						if (z.availableIn == 0)
							return r; 
                        r = f;
						
						z.availableIn--;
                        z.totalIn++;
						b = (z.nextIn[z.nextInIndex++]) & 0xff;
						
						if ((((z._istate._method << 8) + b) % 31) != 0)
						{
							z._istate._mode = _BAD;
							z.message = "Incorrect header check";
							z._istate._marker = 5; // can't try inflateSync
							break;
						}
						
						if ((b & _PRESET_DICT) == 0)
						{
							z._istate._mode = _BLOCKS;
							break;
						}
						z._istate._mode = _DICT4;
						goto case _DICT4;
					
					case _DICT4: 
						
						if (z.availableIn == 0)
							return r;
                        r = f;
						
						z.availableIn--; 
                        z.totalIn++;
						z._istate._need = ((z.nextIn[z.nextInIndex++] & 0xff) << 24) & unchecked((int) 0xff000000L);
						z._istate._mode = _DICT3;
						goto case _DICT3;
					
					case _DICT3: 
						
						if (z.availableIn == 0)
							return r; r = f;
						
						z.availableIn--; z.totalIn++;
						z._istate._need += (((z.nextIn[z.nextInIndex++] & 0xff) << 16) & 0xff0000L);
						z._istate._mode = _DICT2;
						goto case _DICT2;
					
					case _DICT2: 
						
						if (z.availableIn == 0)
							return r;
                        r = f;
						
						z.availableIn--; 
                        z.totalIn++;
						z._istate._need += (((z.nextIn[z.nextInIndex++] & 0xff) << 8) & 0xff00L);
						z._istate._mode = _DICT1;
						goto case _DICT1;
					
					case _DICT1: 
						
						if (z.availableIn == 0)
							return r;
                        
                        r = f;
						z.availableIn--; 
                        z.totalIn++;
						z._istate._need += (z.nextIn[z.nextInIndex++] & 0xffL);
						z.adler = z._istate._need;
						z._istate._mode = _DICT0;
                        return ZLibConst.Status.Z_NEED_DICT;
					
					case _DICT0: 
						z._istate._mode = _BAD;
						z.message = "need dictionary";
						z._istate._marker = 0; // can try inflateSync
                        return ZLibConst.Status.Z_STREAM_ERROR;
					
					case _BLOCKS: 
						
						r = z._istate._blocks.proc(z, r);
                        if (r == ZLibConst.Status.Z_DATA_ERROR)
						{
							z._istate._mode = _BAD;
							z._istate._marker = 0; // can try inflateSync
							break;
						}
                        if (r == ZLibConst.Status.Z_OK)
						{
							r = f;
						}
                        if (r != ZLibConst.Status.Z_STREAM_END)
						{
							return r;
						}
						r = f;
						z._istate._blocks.reset(z, z._istate._was);
						if (z._istate._nowrap != 0)
						{
							z._istate._mode = _DONE;
							break;
						}
						z._istate._mode = _CHECK4;
						goto case _CHECK4;
					
					case _CHECK4: 
						
						if (z.availableIn == 0)
							return r; 
                        r = f;
						
						z.availableIn--; 
                        z.totalIn++;
						z._istate._need = ((z.nextIn[z.nextInIndex++] & 0xff) << 24) & unchecked((int) 0xff000000L);
						z._istate._mode = _CHECK3;
						goto case _CHECK3;
					
					case _CHECK3: 
						
						if (z.availableIn == 0)
							return r;
                        
                        r = f;
						
						z.availableIn--; z.totalIn++;
						z._istate._need += (((z.nextIn[z.nextInIndex++] & 0xff) << 16) & 0xff0000L);
						z._istate._mode = _CHECK2;
						goto case _CHECK2;
					
					case _CHECK2: 
						
						if (z.availableIn == 0)
							return r;
                        
                        r = f;
						
						z.availableIn--; 
                        z.totalIn++;
						z._istate._need += (((z.nextIn[z.nextInIndex++] & 0xff) << 8) & 0xff00L);
						z._istate._mode = _CHECK1;
						goto case _CHECK1;
					
					case _CHECK1: 
						
						if (z.availableIn == 0)
							return r;
                        r = f;
						
						z.availableIn--; z.totalIn++;
						z._istate._need += (z.nextIn[z.nextInIndex++] & 0xffL);
						
						if (((int) (z._istate._was[0])) != ((int) (z._istate._need)))
						{
							z._istate._mode = _BAD;
							z.message = "Incorrect data check";
							z._istate._marker = 5; // can't try inflateSync
							break;
						}
						
						z._istate._mode = _DONE;
						goto case _DONE;
					
					case _DONE:
                        return ZLibConst.Status.Z_STREAM_END;
					
					case _BAD:
                        return ZLibConst.Status.Z_DATA_ERROR;
					
					default:
                        return ZLibConst.Status.Z_STREAM_ERROR;
					
				}
			}
		}
		
		
		internal int InflateSetDictionary(ZStream z, byte[] dictionary, int dictLength)
		{
			int index = 0;
			int length = dictLength;
			if (z == null || z._istate == null || z._istate._mode != _DICT0)
                return ZLibConst.Status.Z_STREAM_ERROR;
			
			if (z._adler.Calculate(1L, dictionary, 0, dictLength) != z.adler)
			{
                return ZLibConst.Status.Z_DATA_ERROR;
			}
			
			z.adler = z._adler.Calculate(0, null, 0, 0);
			
			if (length >= (1 << z._istate._wbits))
			{
				length = (1 << z._istate._wbits) - 1;
				index = dictLength - length;
			}
			z._istate._blocks.set_dictionary(dictionary, index, length);
			z._istate._mode = _BLOCKS;
            return ZLibConst.Status.Z_OK;
		}
		
		internal int InflateSync(ZStream z)
		{
			int n; // number of bytes to look at
			int p; // pointer to bytes
			int m; // number of marker bytes found in a row
			long r, w; // temporaries to save total_in and total_out
			
			// set up
			if (z == null || z._istate == null)
                return ZLibConst.Status.Z_STREAM_ERROR;
			if (z._istate._mode != _BAD)
			{
				z._istate._mode = _BAD;
				z._istate._marker = 0;
			}
			if ((n = z.availableIn) == 0)
                return ZLibConst.Status.Z_BUF_ERROR;
			p = z.nextInIndex;
			m = z._istate._marker;
			
			// search
			while (n != 0 && m < 4)
			{
				if (z.nextIn[p] == _mark[m])
				{
					m++;
				}
				else if (z.nextIn[p] != 0)
				{
					m = 0;
				}
				else
				{
					m = 4 - m;
				}
				p++; n--;
			}
			
			// restore
			z.totalIn += p - z.nextInIndex;
			z.nextInIndex = p;
			z.availableIn = n;
			z._istate._marker = m;
			
			// return no joy or set up to restart on a new block
			if (m != 4)
			{
                return ZLibConst.Status.Z_DATA_ERROR;
			}
			r = z.totalIn; w = z.totalOut;
			InflateReset(z);
			z.totalIn = r; z.totalOut = w;
			z._istate._mode = _BLOCKS;
            return ZLibConst.Status.Z_OK;
		}
		
		// Returns true if inflate is currently at the end of a block generated
		// by Z_SYNC_FLUSH or Z_FULL_FLUSH. This function is used by one PPP
		// implementation to provide an additional safety check. PPP uses Z_SYNC_FLUSH
		// but removes the length bytes of the resulting empty stored block. When
		// decompressing, PPP checks that at the end of input packet, inflate is
		// waiting for these length bytes.
		internal int InflateSyncPoint(ZStream z)
		{
			if (z == null || z._istate == null || z._istate._blocks == null)
                return ZLibConst.Status.Z_STREAM_ERROR;
			return z._istate._blocks.sync_point();
		}
	}
}